﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Web;
using System.Web.Caching;
using System.Xml;
using EPiServer.DynamicContent;
using FortuneCookie.EPiServer.SearchAndReplace.Configuration;
using FortuneCookie.EPiServer.SearchAndReplace.Data;
using EPiServer;
using EPiServer.Core;
using EPiServer.DataAbstraction;
using EPiServer.DataAccess;
using EPiServer.Security;
using EPiServer.SpecializedProperties;
using log4net;
using Sgml;

namespace FortuneCookie.EPiServer.SearchAndReplace
{

    /// <summary>
    /// Search and replace helper class
    /// </summary>
    internal static class SearchAndReplaceHelper
    {

        #region Members

        private static readonly ILog Logger = LogManager.GetLogger(typeof(SearchAndReplaceHelper));

        #endregion Members

        #region Enums

        /// <summary>
        /// Update type enumeration
        /// </summary>
        public enum UpdateType
        {
            None,
            PageSearch,
            PageUpdate,
            DynamicPropertyUpdate
        }

        #endregion Enums

        #region Methods

        public static void SearchAndReplace(SearchAndReplace searchAndReplace)
        {
            Thread thread = new Thread(FindAndReplaceThreaded)
                                {
                                    IsBackground = false,
                                    Priority = ThreadPriority.Normal
                                };
            thread.Start(searchAndReplace);
        }

        private static void FindAndReplaceThreaded(object o)
        {
            SearchAndReplace searchAndReplace = o as SearchAndReplace;
            AddToCache(searchAndReplace, UpdateType.None);

            // if no language branch has been selected and this is a globalised site
            if (string.IsNullOrEmpty(searchAndReplace.LanguageBranch) && global::EPiServer.Configuration.Settings.Instance.UIShowGlobalizationUserInterface)
            {
                foreach (LanguageBranch languageBranch in LanguageBranch.ListEnabled())
                {
                    string language = languageBranch.LanguageID;
                    PageData startPage = GetPage(int.Parse(searchAndReplace.StartPage), language);

                    if (startPage == null)
                        continue;

                    FindAndReplaceRecursive(startPage, searchAndReplace, language, true);
                }
            }
            else
            {
                string language = searchAndReplace.LanguageBranch;

                if (string.IsNullOrEmpty(language))
                    language = DataFactory.Instance.GetPage(PageReference.StartPage).MasterLanguageBranch;

                PageData startPage = GetPage(int.Parse(searchAndReplace.StartPage), language);

                if (startPage != null)
                    FindAndReplaceRecursive(startPage, searchAndReplace, language, true);
            }

            searchAndReplace.Completed = true;
            IDataHelper dataHelper = DataHelperFactory.CreateDataHelper();
            dataHelper.Update(searchAndReplace);
            RemoveFromCache(searchAndReplace);
        }

        private static void FindAndReplaceRecursive(PageData page, SearchAndReplace searchAndReplace, string languageBranch)
        {
            FindAndReplaceRecursive(page, searchAndReplace, languageBranch, false);    
        }

        private static void FindAndReplaceRecursive(PageData page, SearchAndReplace searchAndReplace, string languageBranch, bool startPage)
        {
            if (page == null)
                return;

            // if the current page type name is not exclude find and replace
            if (!ConfigurationHelper.ExcludedPageTypeNames.Contains(page.PageTypeName))
                FindAndReplace(page, searchAndReplace, languageBranch);

            // if only the start page should be searched return
            if (startPage && searchAndReplace.StartPageOnly)
                return;

            // Recurively find and replace
            foreach (PageData childPage in GetChildPages(page, languageBranch))
                FindAndReplaceRecursive(childPage, searchAndReplace, languageBranch);
        }

        private static void FindAndReplace(PageData pageData, SearchAndReplace searchAndReplace, string languageBranch)
        {
            // if the page type is excluded return
            if (ConfigurationHelper.ExcludedPageTypeNames.Contains(pageData.PageTypeName))
                return;

            Debug.WriteLine(string.Format("{0}Page being scanned : {1} [{2}_{3}]", Environment.NewLine,
                pageData.PageName, pageData.PageLink.ID, pageData.PageLink.WorkID));
            Stopwatch pageScanStopWatch = new Stopwatch();
            pageScanStopWatch.Start();

            bool updated = false;
            PageData currentPage = pageData.CreateWritableClone();

            // Handle page properties
            UpdatePageProperties(currentPage, searchAndReplace, ref updated);

            pageScanStopWatch.Stop();
            Debug.WriteLine(string.Format("Time taken to scan page {1} [{2}_{3}] : {4} milliseconds{0}", Environment.NewLine,
                pageData.PageName, pageData.PageLink.ID, pageData.PageLink.WorkID, pageScanStopWatch.ElapsedMilliseconds));

            AddToCache(searchAndReplace, UpdateType.PageSearch);

            Page page = new Page
            {
                Id = Guid.NewGuid(),
                LanguageBranch = currentPage.LanguageBranch,
                PageId = currentPage.PageLink.ID,
                PreviousWorkId = currentPage.PageLink.WorkID,
                SearchAndReplaceId = searchAndReplace.Id,
                SortIndex = searchAndReplace.Pages.Count
            };

            if (updated)
            {
                Debug.WriteLine(string.Format("Page {1} [{2}_{3}] was updated{0}", Environment.NewLine,
                                              pageData.PageName, pageData.PageLink.ID, pageData.PageLink.WorkID));

                AddToCache(searchAndReplace, UpdateType.PageUpdate);

                PageReference pageReference = DataFactory.Instance.Save(currentPage, SaveAction.Save | SaveAction.ForceNewVersion, AccessLevel.NoAccess);

                //page.WorkId = GetPage(currentPage.PageLink.ID, languageBranch).PageLink.WorkID;
                page.WorkId = pageReference.WorkID;

                if (page.WorkId == page.PreviousWorkId)
                    page.PreviousWorkId = GetPage(currentPage.PageLink.ID, languageBranch, true).PageLink.WorkID;

                searchAndReplace.Pages.Add(page);
                IDataHelper dataHelper = DataHelperFactory.CreateDataHelper();
                dataHelper.Update(searchAndReplace);
            }

            // Handle dynamic properties
            UpdateDynamicProperties(pageData, searchAndReplace, page);
        }

        private static void UpdatePageProperties(PageData currentPage, SearchAndReplace searchAndReplace, ref bool updated)
        {
            if (ConfigurationHelper.SleepDelayBetweenPages > 0)
                Thread.Sleep(ConfigurationHelper.SleepDelayBetweenPages);

            foreach (PropertyData property in currentPage.Property)
            {
                bool propertyUpdated = false;

                Debug.WriteLine(string.Format("Property name : {0}", property.Name));

                if (ConfigurationHelper.ExcludedPropertyNames.Contains(property.Name) || !property.IsPropertyData ||
                    !IsStringProperty(property) || property.IsMetaData)
                    continue;

                if (!currentPage.IsMasterLanguageBranch && !property.IsLanguageSpecific)
                    continue;

                Debug.WriteLine(string.Format("Property being scanned : {0}", property.Name));
                Stopwatch propertyScanStopWatch = new Stopwatch();
                propertyScanStopWatch.Start();

                string updatedValue = IsHtmlString(property) && (!IsHtmlString(searchAndReplace.Find) || (IsHtmlString(searchAndReplace.Find) && !ConfigurationHelper.AllowMarkupToBeReplacedIfFindTextIsMarkup))
                                          ? FindAndReplaceInHtmlString(property.Value.ToString(), searchAndReplace, ref propertyUpdated)
                                          : FindAndReplace(property.Value.ToString(), searchAndReplace, ref propertyUpdated);

                propertyScanStopWatch.Stop();
                Debug.WriteLine(string.Format("Time taken to scan property {0} : {1} milliseconds", property.Name,
                                              propertyScanStopWatch.ElapsedMilliseconds));

                if (!propertyUpdated)
                    continue;

                if (property.Type == PropertyDataType.String && updatedValue.Length > 255)
                {
                    AddError(string.Format("The page property '{0}' length cannot be greater than 255 characters.", property.Name),
                        currentPage, property, searchAndReplace, currentPage.LanguageBranch, true);
                    continue;
                }

                try
                {
                    UpdatePropertyValue(property, updatedValue);
                }
                catch (Exception ex)
                {
                    AddError(ex.Message, currentPage, property, searchAndReplace, currentPage.LanguageBranch, true);
                    continue;
                }
                
                updated = true;
                Debug.WriteLine(string.Format("Property {0} was updated.", property.Name));
            }
        }

        private static void UpdatePropertyValue(PropertyData property, string value)
        {
            if (property.Type == PropertyDataType.LinkCollection)
            {
                PropertyLinkCollection linkCollectionProperty = property as PropertyLinkCollection;
                linkCollectionProperty.LoadData(value);
            }
            else
                property.Value = value;
        }

        private static void UpdateDynamicProperties(PageData pageData, SearchAndReplace searchAndReplace, Page page)
        {
            if (!searchAndReplace.UpdateDynamicProperties) 
                return;

            DynamicPropertyCollection dynamicProperties = global::EPiServer.DataAbstraction.DynamicProperty.ListForPage(pageData.PageLink);

            foreach (global::EPiServer.DataAbstraction.DynamicProperty dynamicProperty in dynamicProperties)
            {
                if (dynamicProperty.PropertyValue == null || dynamicProperty.PropertyValue.Value == null)
                    continue;

                PropertyData property = dynamicProperty.PropertyValue;

                if (ConfigurationHelper.ExcludedPropertyNames.Contains(property.Name) || !property.IsPropertyData || !IsStringProperty(property) || property.IsMetaData)
                    continue;

                if (property.IsLanguageSpecific &&
                    !string.IsNullOrEmpty(searchAndReplace.LanguageBranch) &&
                    !string.IsNullOrEmpty(dynamicProperty.LanguageBranch) && 
                    searchAndReplace.LanguageBranch != dynamicProperty.LanguageBranch)
                    continue;

                bool propertyUpdated = false;
                Debug.WriteLine(string.Format("Dynamic property being scanned : {0}", property.Name));
                Stopwatch propertyScanStopWatch = new Stopwatch();
                propertyScanStopWatch.Start();

                string updatedValue = IsHtmlString(property) && (!IsHtmlString(searchAndReplace.Find) || (IsHtmlString(searchAndReplace.Find) && !ConfigurationHelper.AllowMarkupToBeReplacedIfFindTextIsMarkup))
                                          ? FindAndReplaceInHtmlString(property.Value.ToString(), searchAndReplace, ref propertyUpdated)
                                          : FindAndReplace(property.Value.ToString(), searchAndReplace, ref propertyUpdated);

                propertyScanStopWatch.Stop();
                Debug.WriteLine(string.Format("Time taken to scan dynamic property {0} : {1} milliseconds", property.Name,
                                              propertyScanStopWatch.ElapsedMilliseconds));

                if (!propertyUpdated)
                    continue;

                if (property.Type == PropertyDataType.String && updatedValue.Length > 255)
                {
                    AddError(string.Format("The dynamic property '{0}' length cannot be greater than 255 characters.", property.Name),
                        pageData, property, searchAndReplace, dynamicProperty.LanguageBranch, false);
                    continue;
                }

                try
                {
                    UpdatePropertyValue(dynamicProperty.PropertyValue, updatedValue);
                }
                catch (Exception ex)
                {
                    AddError(ex.Message, pageData, property, searchAndReplace, dynamicProperty.LanguageBranch, false);
                    continue;
                }

                dynamicProperty.Save();
                Debug.WriteLine(string.Format("Dynamic property {0} was updated.", property.Name));

                // get dynamic property language branch
                string dynamicPropertyLanguageBranch = dynamicProperty.LanguageBranch;

                if (string.IsNullOrEmpty(dynamicPropertyLanguageBranch))
                    dynamicPropertyLanguageBranch = pageData.MasterLanguageBranch;

                // get dynamic property name
                string dynamicPropertyName = dynamicProperty.PropertyValue.Name;
                PageDefinition pageDefinition = PageDefinition.ListDynamic().Where(current => current.Name == dynamicPropertyName).FirstOrDefault();

                if (pageDefinition != null)
                    dynamicPropertyName += string.Format(" ({0})", pageDefinition.EditCaption);

                DynamicProperty dynProperty = new DynamicProperty
                {
                    Id = Guid.NewGuid(),
                    LanguageBranch = dynamicPropertyLanguageBranch,
                    PageId = page.PageId,
                    WorkId = page.WorkId,
                    SearchAndReplaceId = searchAndReplace.Id,
                    PropertyName = dynamicPropertyName,
                    SortIndex = searchAndReplace.DynamicProperties.Count
                };

                searchAndReplace.DynamicProperties.Add(dynProperty);
                IDataHelper dataHelper = DataHelperFactory.CreateDataHelper();
                dataHelper.Update(searchAndReplace);

                AddToCache(searchAndReplace, UpdateType.DynamicPropertyUpdate);
            }
        }

        private static void AddError(string message, PageData page, PropertyData property, SearchAndReplace searchAndReplace, string languageBranch, bool pageProperty)
        {
            Error error = new Error
            {
                ErrorMessage = message,
                LanguageBranch = languageBranch,
                PageId = page.PageLink.ID,
                WorkId = page.PageLink.WorkID,
                PageProperty = pageProperty,
                PropertyName = property.Name,
                SearchAndReplaceId = searchAndReplace.Id,
                SortIndex = searchAndReplace.Errors.Count
            };
            searchAndReplace.Errors.Add(error);
            IDataHelper dataHelper = DataHelperFactory.CreateDataHelper();
            dataHelper.Update(searchAndReplace);
        }

        private static string FindAndReplace(string value, SearchAndReplace searchAndReplace, ref bool updated)
        {
            string updatedValue = value;

            Regex regex = GetRegularExpression(searchAndReplace);
            updatedValue = regex.Replace(updatedValue, searchAndReplace.Replace);
            updated = !string.Equals(value, updatedValue);

            return updatedValue;
        }

        private static string FindAndReplaceInHtmlString(string value, SearchAndReplace searchAndReplace, 
            ref bool updated)
        {
            StringReader reader = new StringReader(string.Format("<root>{0}</root>", value));
            SgmlReader sgmlReader = new SgmlReader
                                             {
                                                 DocType = "HTML",
                                                 WhitespaceHandling = WhitespaceHandling.All,
                                                 CaseFolding = CaseFolding.None,
                                                 InputStream = reader,
                                             };

            // create document
            XmlDocument doc = new XmlDocument { PreserveWhitespace = true, XmlResolver = null };
            doc.Load(sgmlReader);

            XmlNodeList nodes = doc.SelectNodes("//*");

            // Update dynamic content nodes
            bool updatedNode = HandleDynamicContent(doc, searchAndReplace);

            if (updatedNode)
                updated = true;

            if (nodes != null)
            {
                foreach (XmlNode node in nodes)
                {
                    // skip dynamic content nodes as these have already been dealt with
                    if (string.Equals(node.Name, "span", StringComparison.OrdinalIgnoreCase) && node.Attributes["class"] != null && node.Attributes["class"].Value == "dynamiccontent")
                        continue;

                    updatedNode = false;
                    XmlNodeList textNodes = node.SelectNodes("text()");

                    if (textNodes != null)
                    {
                        foreach (XmlNode textNode in textNodes)
                        {
                            string textValue = HttpUtility.HtmlEncode(textNode.InnerText);
                            string newValue = FindAndReplace(textValue, searchAndReplace, ref updatedNode);

                            try
                            {
                                if (IsHtmlString(newValue))
                                {
                                    XmlReader xmlReader = XmlReader.Create(new StringReader("<r>" + newValue + "</r>"));
                                    XmlNode newNode = doc.ReadNode(xmlReader);
                                    xmlReader.Close();

                                    if (newNode != null)
                                    {
                                        XmlNode nodeToInsertAfter = textNode;

                                        foreach (XmlNode currentNode in newNode.ChildNodes.Cast<XmlNode>().ToList())
                                        {
                                            textNode.ParentNode.InsertAfter(currentNode, nodeToInsertAfter);
                                            nodeToInsertAfter = currentNode;
                                        }

                                        textNode.ParentNode.RemoveChild(textNode);
                                    }
                                }
                                else
                                    textNode.InnerText = HttpUtility.HtmlDecode(newValue);
                            }
                            catch (Exception)
                            {
                                textNode.InnerText = HttpUtility.HtmlDecode(newValue);
                            }
                            
                            if (updatedNode)
                                updated = true;
                        }
                    }

                    foreach (XmlAttribute attribute in node.Attributes)
                    {
                        updatedNode = false;
                        attribute.Value = FindAndReplace(attribute.Value, searchAndReplace, ref updatedNode);

                        if (updatedNode)
                            updated = true;
                    }
                }
            }

            string xml = doc.OuterXml.Trim().Substring(6);
            xml = xml.Substring(0, xml.Length - 7);
            sgmlReader.Close();
            reader.Dispose();
            return updated ? HttpUtility.HtmlDecode(xml) : value;
        }

        private static bool HandleDynamicContent(XmlNode dynamicContentNode, SearchAndReplace searchAndReplace)
        {
            if (dynamicContentNode == null)
                return false;

            bool updated = false;
            string name = dynamicContentNode.Attributes["dynamicclass"].Value;
            string state = dynamicContentNode.Attributes["state"].Value;
            string hash = string.Empty;

            bool generateHash = dynamicContentNode.Attributes["hash"] != null;

            if (generateHash)
                hash = dynamicContentNode.Attributes["hash"].Value;

            IDynamicContent dynamicContent = generateHash ? DynamicContentFactory.Instance.CreateDynamicContent(name, state, hash, true) 
                                                          : DynamicContentFactory.Instance.CreateDynamicContent(name, state, string.Empty, false);

            foreach (PropertyData propertyData in dynamicContent.Properties)
            {
                if (ConfigurationHelper.ExcludedPropertyNames.Contains(propertyData.Name) || !IsStringProperty(propertyData))
                    continue;

                bool propertyUpdated = false;
                string updatedValue = IsHtmlString(propertyData) && (!IsHtmlString(searchAndReplace.Find) || (IsHtmlString(searchAndReplace.Find) && !ConfigurationHelper.AllowMarkupToBeReplacedIfFindTextIsMarkup))
                                          ? FindAndReplaceInHtmlString(propertyData.Value.ToString(), searchAndReplace, ref propertyUpdated)
                                          : FindAndReplace(propertyData.Value.ToString(), searchAndReplace, ref propertyUpdated);

                if (!propertyUpdated) 
                    continue;

                updated = true;
                propertyData.Value = updatedValue;
            }

            StringReader reader = new StringReader(string.Format("<root>{0}</root>", DynamicContentFactory.Instance.GetMarkup(dynamicContent, generateHash)));
            SgmlReader sgmlReader = new SgmlReader
            {
                DocType = "HTML",
                WhitespaceHandling = WhitespaceHandling.All,
                CaseFolding = CaseFolding.None,
                InputStream = reader,
            };

            // create document
            XmlDocument doc2 = new XmlDocument { PreserveWhitespace = true, XmlResolver = null };
            doc2.Load(sgmlReader);
            dynamicContentNode.ParentNode.ReplaceChild(dynamicContentNode.OwnerDocument.ImportNode(doc2.DocumentElement.FirstChild, true), dynamicContentNode);
            sgmlReader.Close();
            reader.Dispose();
            return updated;
        }

        private static bool HandleDynamicContent(XmlDocument doc, SearchAndReplace searchAndReplace)
        {
            bool updated = false;
            XmlNodeList dynamicContentNodes = doc.SelectNodes("//span[@class='dynamiccontent']");

            if (dynamicContentNodes == null) 
                return false;

            foreach (XmlNode dynamicContentNode in dynamicContentNodes)
            {
                bool updatedNode = HandleDynamicContent(dynamicContentNode, searchAndReplace);

                if (updatedNode)
                    updated = true;
            }

            return updated;
        }

        private static bool IsStringProperty(PropertyData property)
        {
            return (property.Value != null && (property.Type == PropertyDataType.String || property.Type == PropertyDataType.LongString || property.Type == PropertyDataType.LinkCollection));
        }

        private static bool IsHtmlString(PropertyData property)
        {
            return (property.Value != null && Regex.IsMatch(property.Value.ToString(), "<(.|\n)*?>"));
        }

        private static bool IsHtmlString(string value)
        {
            return Regex.IsMatch(value, "<(.|\n)*?>");
        }

        private static PageData GetPage(int pageId, string languageBranch)
        {
            return GetPage(pageId, languageBranch, false);
        }

        private static PageData GetPage(int pageId, string languageBranch, bool previous)
        {
            ILanguageSelector languageSelector = new LanguageSelector(languageBranch);
            PageVersionCollection versions = PageVersion.List(new PageReference(pageId), languageBranch);
            int index = versions.Count - (previous && versions.Count > 1 ? 2 : 1);
            return versions.Count == 0 ? null : DataFactory.Instance.GetPage(versions[index].ID, 
                languageSelector);
        }

        private static IEnumerable<PageData> GetChildPages(PageData page, string languageBranch)
        {
            // Get all pages (published/unpublished)
            PropertyCriteria criteria = new PropertyCriteria
            {
                Type = PropertyDataType.PageReference,
                Value = page.PageLink.ID.ToString(),
                Name = "PageParentLink",
                Condition = global::EPiServer.Filters.CompareCondition.Equal
            };

            ILanguageSelector languageSelector = new LanguageSelector(languageBranch);
            PropertyCriteriaCollection criteriaCollection = new PropertyCriteriaCollection { criteria };
            PageDataCollection pages = DataFactory.Instance.FindAllPagesWithCriteria(page.PageLink, criteriaCollection, languageBranch, languageSelector);

            // Get current versions of all pages and replace in page data collection
            for (int i = 0; i < pages.Count; i++)
            {
                PageVersionCollection versions = PageVersion.List(pages[i].PageLink, languageBranch);
                pages[i] = DataFactory.Instance.GetPage(versions[versions.Count - 1].ID, languageSelector);
            }

            return pages;
        }

        public static bool CanPublish(Page page)
        {
            PageVersionCollection versions = PageVersion.List(new PageReference(page.PageId), page.LanguageBranch);

            int index = -1;

            for (int i = 0; i < versions.Count; i++)
            {
                if (versions[i].ID.ID != page.PageId || versions[i].ID.WorkID != page.WorkId) 
                    continue;
                
                index = i;
                break;
            }

            if (index == -1)
                return false;

            if (versions[index].Status == VersionStatus.Published)
                return false;

            bool canPublish = false;

            for (int i = 0; i < index; i++)
            {
                if (versions[i].Status != VersionStatus.PreviouslyPublished && versions[i].Status != VersionStatus.Published)
                    continue;

                canPublish = true;
                break;
            }

            if (!canPublish)
                return false;

            for (int i = (index + 1); i < versions.Count; i++)
            {
                if (versions[i].Status != VersionStatus.Published)
                    continue;

                canPublish = false;
                break;
            }

            return canPublish;
        }

        public static bool CanRollback(Page page)
        {
            PageVersionCollection versions = PageVersion.List(new PageReference(page.PageId), page.LanguageBranch);
            PageVersion version = versions.Find(current => current.ID.WorkID == page.WorkId);
            PageVersion previousVersion = versions.Find(current => current.ID.WorkID == page.PreviousWorkId);
            return (version != null && version.Status != VersionStatus.Published && versions.Count > 1 && previousVersion != null);
        }

        public static void Publish(List<Page> pagesToPublish)
        {
            Thread thread = new Thread(PublishThreaded)
            {
                IsBackground = false,
                Priority = ThreadPriority.Normal
            };
            thread.Start(pagesToPublish);
        }

        private static void PublishThreaded(object o)
        {
            List<Page> pagesToPublish = o as List<Page>;

            if (pagesToPublish.Count == 0)
                return;

            SearchAndReplace searchAndReplace = new SearchAndReplace {Id = pagesToPublish[0].SearchAndReplaceId};
            AddToCache(searchAndReplace, UpdateType.None);

            foreach (Page page in pagesToPublish.Where(CanPublish))
            {
                if (ConfigurationHelper.SleepDelayBetweenPages > 0)
                    Thread.Sleep(ConfigurationHelper.SleepDelayBetweenPages);

                try
                {
                    ILanguageSelector languageSelector = new LanguageSelector(page.LanguageBranch);
                    PageData pageData = DataFactory.Instance.GetPage(new PageReference(page.PageId, page.WorkId), languageSelector);
                    DataFactory.Instance.Save(pageData.CreateWritableClone(), SaveAction.Publish, AccessLevel.NoAccess);
                    AddToCache(new SearchAndReplace { Id = pagesToPublish[0].SearchAndReplaceId }, UpdateType.PageUpdate);
                }
                catch (Exception ex)
                {
                    Logger.Error("An exception occured in the SearchAndReplaceHelper :: PublishThreaded", ex);
                }
            }

            RemoveFromCache(searchAndReplace);
        }

        public static void Rollback(List<Page> pagesToRollback)
        {
            Thread thread = new Thread(RollbackThreaded)
            {
                IsBackground = false,
                Priority = ThreadPriority.Normal
            };
            thread.Start(pagesToRollback);
        }

        private static void RollbackThreaded(object o)
        {
            List<Page> pagesToRollback = o as List<Page>;

            if (pagesToRollback.Count == 0)
                return;

            SearchAndReplace searchAndReplace = new SearchAndReplace { Id = pagesToRollback[0].SearchAndReplaceId };
            AddToCache(searchAndReplace, UpdateType.None);
            
            foreach (Page page in pagesToRollback.Where(CanRollback))
            {
                if (ConfigurationHelper.SleepDelayBetweenPages > 0)
                    Thread.Sleep(ConfigurationHelper.SleepDelayBetweenPages);

                try
                {
                    DataFactory.Instance.DeleteVersion(new PageReference(page.PageId, page.WorkId), AccessLevel.NoAccess);
                    AddToCache(new SearchAndReplace { Id = pagesToRollback[0].SearchAndReplaceId }, UpdateType.PageUpdate);
                }
                catch (Exception ex)
                {
                    Logger.Error("An exception occured in the SearchAndReplaceHelper :: RollbackThreaded", ex);
                }
            }

            RemoveFromCache(searchAndReplace);
        }

        public static bool IsPublished(Page page)
        {
            ILanguageSelector languageSelector = new LanguageSelector(page.LanguageBranch);
            PageData pageData = null;

            try
            {
                pageData = DataFactory.Instance.GetPage(new PageReference(page.PageId, page.WorkId), languageSelector);
            }
            catch (PageNotFoundException)
            {
            }
                    
            return (pageData == null || (pageData.Status != VersionStatus.Published && pageData.Status != VersionStatus.PreviouslyPublished) ? false : true);
        }

        public static bool IsRolledBack(Page page)
        {
            ILanguageSelector languageSelector = new LanguageSelector(page.LanguageBranch);
            PageData pageData = null;

            try
            {
                pageData = DataFactory.Instance.GetPage(new PageReference(page.PageId, page.WorkId), languageSelector);
            }
            catch (PageNotFoundException)
            {
            }

            return (pageData == null ? true : false);
        }

        public static string GetPageDisplayValue(string pageId, string languageBranch)
        {
            if (string.IsNullOrEmpty(pageId) || !IsInt32(pageId))
                return string.Empty;

            if (string.IsNullOrEmpty(languageBranch))
                languageBranch = DataFactory.Instance.GetPage(PageReference.StartPage).MasterLanguageBranch;

            ILanguageSelector languageSelector = new LanguageSelector(languageBranch);
            PageData pageData;

            try
            {
                pageData = DataFactory.Instance.GetPage(new PageReference(int.Parse(pageId)), languageSelector);
            }
            catch (PageNotFoundException)
            {
                return "Page does not exist";
            }

            return pageData != null ? string.Format("{0} [{1}]", pageData.PageName, pageData.PageLink.ID) : string.Empty;
        }

        public static bool IsInt32(string value)
        {
            int parsedInt;
            return int.TryParse(value, out parsedInt);
        }

        private static Regex GetRegularExpression(SearchAndReplace searchAndReplace)
        {
            string pattern = Regex.Escape(searchAndReplace.Find);

            if (!searchAndReplace.SearchWithinWords)
            {
                if (Regex.IsMatch(searchAndReplace.Find, @"^\w"))
                    pattern = @"\b" + pattern;

                if (Regex.IsMatch(searchAndReplace.Find, @"\w$"))
                    pattern += @"\b";
            }

            return (searchAndReplace.MatchCase ? new Regex(pattern, RegexOptions.Multiline)
                                               : new Regex(pattern, RegexOptions.Multiline | RegexOptions.IgnoreCase));
        }        

        public static void AddToCache(SearchAndReplace searchAndReplace, UpdateType updateType)
        {
            int[] values = GetFromCache<int[]>(searchAndReplace) ?? new[] {0, 0, 0};

            switch (updateType)
            {
                case UpdateType.PageSearch:
                    values[0] = values[0] + 1;
                    break;
                case UpdateType.PageUpdate:
                    values[1] = values[1] + 1;
                    break;
                case UpdateType.DynamicPropertyUpdate:
                    values[2] = values[2] + 1;
                    break;
            }

            Debug.WriteLine(string.Format("Pages scanned : {0}", values[0]));
            Debug.WriteLine(string.Format("Pages updated : {0}", values[1]));
            Debug.WriteLine(string.Format("Dynamic properties updated : {0}", values[2]));

            HttpRuntime.Cache.Insert(GetCacheKey(searchAndReplace), values, null, DateTime.Now.AddMinutes(2), Cache.NoSlidingExpiration);
        }

        public static T GetFromCache<T>(SearchAndReplace searchAndReplace)
        {
            return (T)HttpRuntime.Cache[GetCacheKey(searchAndReplace)];
        }

        private static void RemoveFromCache(SearchAndReplace searchAndReplace)
        {
            HttpRuntime.Cache.Remove(GetCacheKey(searchAndReplace));
        }

        private static string GetCacheKey(SearchAndReplace searchAndReplace)
        {
            return string.Format("SearchAndReplace_{0}", searchAndReplace.Id);
        }

        #endregion Methods

    }
}
